/* * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.flex.compiler.ant; import java.io.File; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.List; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; import org.apache.tools.ant.types.Commandline; import org.apache.tools.ant.types.Environment.Variable; import org.apache.tools.ant.types.Path; import org.apache.tools.ant.taskdefs.Java; import org.apache.flex.compiler.ant.config.ConfigVariable; import org.apache.flex.compiler.ant.config.NestedAttributeElement; import org.apache.flex.compiler.ant.config.IOptionSource; import org.apache.flex.compiler.ant.config.OptionSpec; /** * This class contains common data and logic used by all the Flex Ant tasks. */ public abstract class FlexTask extends Java { protected static OptionSpec RUNTIME_SHARED_LIBRARY_PATH = new OptionSpec("runtime-shared-library-path", "rslp"); /** * Constructor. * * @param taskName The name of the Ant task. * @param configVariables An array of ConfigVariables that will be set by attributes of the task. * @param toolJARFileName The build tool's jar file. * @param toolClassName The build tool's class name. * @param toolMethodName The build tool's method name. * @param toolFailureMethodName The build tool's method to determine * whether an exit code means failure. */ protected FlexTask(String taskName, ConfigVariable[] configVariables, String toolJARFileName, String toolClassName, String toolMethodName, String toolFailureMethodName) { this.taskName = taskName; this.configVariables = configVariables; this.toolJARFileName = toolJARFileName; this.toolClassName = toolClassName; this.toolMethodName = toolMethodName; this.toolFailureMethodName = toolFailureMethodName; cmdline = new Commandline(); } /** * The name of the Ant task. */ private final String taskName; /** * An array of ConfigVariabes that are set by setDynamicAttribute(). */ protected final ConfigVariable[] configVariables; /** * The build tool's jar file name. */ private final String toolJARFileName; /** * The build tool's class name. */ private final String toolClassName; /** * The build tool's entry point. * It must be a static method that takes a String[] * and returns an int exit code. */ private final String toolMethodName; /** * The build tools' method that determines whether the * exit code should make the Ant task fail. * It must be a static method that takes an int * and returns a boolean. */ private final String toolFailureMethodName; /** * The commandline used in execute() */ protected final Commandline cmdline; protected List<IOptionSource> nestedAttribs; /** * fork attribute */ protected boolean fork; private ClassLoader originalContextClassLoader; protected NestedAttributeElement createElem(String attrib, OptionSpec spec) { NestedAttributeElement e = new NestedAttributeElement(attrib, spec, this); nestedAttribs.add(e); return e; } protected NestedAttributeElement createElem(String[] attribs, OptionSpec spec) { NestedAttributeElement e = new NestedAttributeElement(attribs, spec, this); nestedAttribs.add(e); return e; } protected NestedAttributeElement createElemAllowAppend(String[] attribs, OptionSpec spec) { NestedAttributeElement e = new NestedAttributeElement(attribs, spec, this, true); nestedAttribs.add(e); return e; } /*=======================================================================* * Static Attributes * *=======================================================================*/ /** * Sets whether to run the task in a separate VM. * * @param f if true then run in a separate VM. */ public void setFork(boolean f) { super.setFork(f); this.fork = f; } /*=======================================================================* * Dynamic Attributes * *=======================================================================*/ /** * Set the named attribute to the given value. * * @param attributeName The name of the attribute to set * @param value The value to set the named attribute to */ public void setDynamicAttribute(String attributeName, String value) { ConfigVariable var = null; for (int i = 0; i < configVariables.length && var == null; i++) { if (configVariables[i].matches(attributeName)) var = configVariables[i]; } if (var != null) { var.set(value); } else { throw new BuildException("The <" + taskName + "> type doesn't support the \"" + attributeName + "\" attribute.", getLocation()); } } /*=======================================================================* * Dynamic Elements * *=======================================================================*/ public Object createDynamicElement(String elementName) { ConfigVariable var = null; for (int i = 0; i < configVariables.length && var == null; i++) { if (configVariables[i].matches(elementName)) var = configVariables[i]; } if (var != null) { return createElem(elementName, var.getSpec()); } else { throw new BuildException("The <" + taskName + "> type doesn't support the \"" + elementName + "\" nested element.", getLocation()); } } /*=======================================================================* * Execute and Related Functions * *=======================================================================*/ /** * Called by execute after the set ConfigVariables in <code>vars</code> has * been added to the commandline. This function is responsible for adding * all tool-specific options to the commandline as well as setting the * default options of a build tool. */ protected abstract void prepareCommandline() throws BuildException; /** * Execute the task * * @throws BuildException If running build tool failed */ public final void execute() throws BuildException { String flexHomeProperty = getProject().getProperty("FLEX_HOME"); if (flexHomeProperty == null) throw new BuildException("FLEX_HOME must be set to use the Flex Ant Tasks"); String falconHomeProperty = getProject().getProperty("FALCON_HOME"); if (falconHomeProperty == null) throw new BuildException("FALCON_HOME must be set to use the Flex Ant Tasks"); System.setProperty("FLEX_HOME", flexHomeProperty); String flexlibProperty = flexHomeProperty.concat("/frameworks/"); System.setProperty("flexlib", flexlibProperty); final Variable variable = new Variable(); variable.setKey("flexlib"); variable.setValue(flexHomeProperty); addSysproperty(variable); // This allows the tool to find the default config file. cmdline.createArgument().setValue("+flexlib=" + flexlibProperty); prepareCommandline(); if (fork) executeOutOfProcess(); else executeInProcess(); } /** * Executes the task in a separate VM */ private void executeOutOfProcess() throws BuildException { try { // Without this, the tool class won't be found by executeJava(). Class<?> toolClass = resolveClass(toolClassName); super.setClassname(toolClassName); // convert arguments into a string for use by executeJava() // also auto-quotes arguments with spaces String line = Commandline.toString(cmdline.getArguments()); super.createArg().setLine(line); int exitCode = super.executeJava(); // Check exit code. if (isFatalFailure(toolClass, exitCode)) throw new BuildException(taskName + " task failed."); } finally { if (originalContextClassLoader != null) Thread.currentThread().setContextClassLoader(originalContextClassLoader); } } /** * Executes the task in the same VM */ private void executeInProcess() throws BuildException { try { Class<?> toolClass = resolveClass(toolClassName); log("FlexTask.execute: " + cmdline, Project.MSG_DEBUG); int exitCode = -1; try { Method toolMethod = toolClass.getMethod(toolMethodName, new Class[] {String[].class}); Object result = toolMethod.invoke(null, new Object[] {cmdline.getArguments()}); exitCode = ((Integer)result).intValue(); } catch (Exception e) { StringWriter stringWriter = new StringWriter(); PrintWriter printWriter = new PrintWriter(stringWriter); e.printStackTrace(printWriter); log(stringWriter.toString(), Project.MSG_DEBUG); throw new BuildException("Unable to run " + toolMethodName + ": " + e.getMessage(), e); } if (isFatalFailure(toolClass, exitCode)) throw new BuildException(taskName + " task failed");// } } finally { if (originalContextClassLoader != null) Thread.currentThread().setContextClassLoader(originalContextClassLoader); } } private boolean isFatalFailure(Class<?> toolClass, int exitCode) { boolean fatal = true; try { Method toolFailureMethod = toolClass.getMethod(toolFailureMethodName, new Class[] {int.class}); Object result = toolFailureMethod.invoke(null, new Object[] {exitCode}); fatal = ((Boolean)result).booleanValue(); } catch (Exception e) { StringWriter stringWriter = new StringWriter(); PrintWriter printWriter = new PrintWriter(stringWriter); e.printStackTrace(printWriter); log(stringWriter.toString(), Project.MSG_DEBUG); throw new BuildException("Unable to run " + toolFailureMethodName + ": " + e.getMessage(), e); } return fatal; } private Class<?> resolveClass(String className) { Class<?> result = null; try { result = Class.forName(className, true, Thread.currentThread().getContextClassLoader()); } catch (ClassNotFoundException ignoredClassNotFoundException) { String flexHomeProperty = getProject().getProperty("FALCON_HOME"); if (flexHomeProperty != null) { File flexHome = new File(flexHomeProperty); if ( flexHome.exists() ) { File jarFile = new File(flexHome + "/lib", toolJARFileName); if (jarFile.exists()) { try { URL url = jarFile.toURI().toURL(); URLClassLoader urlClassLoader = new URLClassLoader(new URL[] {url}); result = Class.forName(className, true, urlClassLoader); originalContextClassLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(urlClassLoader); if (fork) super.setClasspath(new Path(getProject(), jarFile.getAbsolutePath())); } catch (MalformedURLException malformedURLException) { // We shouldn't really get here, but just in case. malformedURLException.printStackTrace(); } catch (ClassNotFoundException classNotFoundException) { throw new BuildException("The class " + className + " was not found in jar file " + toolJARFileName, getLocation()); } } else { throw new BuildException("File does not exist: " + toolJARFileName, getLocation()); } } else { throw new BuildException("FALCON_HOME does not exist.", getLocation()); } } else { throw new BuildException("The class, " + className + ", must be in the classpath or the FALCON_HOME property must be set.", getLocation()); } } return result; } }